package ch.il07.converter.persistence.impl.mysql.pooling;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashSet;

import ch.il07.converter.persistence.exceptions.PersistenceException;

/**
 * 
 * @author ncaspar
 * @author phi (at) gress.ly
 */
public class DbConnectionPoolPair {

	private HashSet<Connection> freeConnections = new HashSet<Connection>();
	private HashSet<Connection> busyConnections = new HashSet<Connection>();
	private int maxConnections;
	private String dbUrl, username, password;
	private boolean autoCommit;

	public DbConnectionPoolPair(String dbUrl, String username, String password,
			int maxConnections) {
		this(dbUrl, username, password, true, maxConnections);
	}

	public DbConnectionPoolPair(String dbUrl, String username, String password,
			boolean autoCommit, int maxConnections) {
		if (maxConnections <= 0) {
			throw new IllegalArgumentException("maxConnections muss"
					+ "grösser als 0 sein.");
		}
		this.maxConnections = maxConnections;
		this.dbUrl = dbUrl;
		this.username = username;
		this.password = password;
		this.autoCommit = autoCommit;
	}

	public synchronized Connection getConnection() throws PersistenceException {
		removeClosedConnections();
		if (freeConnections.isEmpty()
				&& busyConnections.size() >= maxConnections) {
			throw new PersistenceException(
					"No more free connections available");
		}
		ensureFreePoolNotEmpty();
		Connection con = freeConnections.iterator().next(); // get Top Element
		freeConnections.remove(con);
		busyConnections.add(con);
		return con;
	}

	public synchronized void putConnection(Connection con) throws SQLException {
		if (con != null && !con.isClosed() && busyConnections.contains(con)) {
			con.setAutoCommit(autoCommit);
			con.setReadOnly(false);
			busyConnections.remove(con);
			freeConnections.add(con);
		}
	}

	/**
	 * If free-pool is empty add a new connection. Attention: This method does
	 * NOT check, if "maxConnections" is reached!
	 * 
	 * @throws PersistenceException
	 * 
	 * @throws SQLException
	 */
	private void ensureFreePoolNotEmpty() throws PersistenceException {
		if (freeConnections.isEmpty()) {
			Connection con;
			try {
				con = DriverManager.getConnection(dbUrl, username, password);
				con.setAutoCommit(autoCommit);
			} catch (SQLException e) {
				throw new PersistenceException(e.getMessage());
			}
			freeConnections.add(con);
		}
	}

	private void removeClosedConnections() throws PersistenceException {
		removeClosedConnections(busyConnections);
		removeClosedConnections(freeConnections);
	}

	private synchronized void removeClosedConnections(
			Collection<Connection> pool) throws PersistenceException {
		// avoid iterator problems
		Connection[] poolCopy = pool.toArray(new Connection[0]);
		try {
			for (Connection con : poolCopy) {
				if (con != null && con.isClosed()) {
					pool.remove(con);
				}
			}
		} catch (SQLException e) {
			throw new PersistenceException(e.getMessage());
		}
	}

	/**
	 * Return the total number of connections (busy + free)
	 */
	public synchronized int size() {
		return freeConnections.size() + busyConnections.size();
	}

	@Override
	public String toString() {
		return "Free connections: " + freeConnections.size() + "\n"
				+ "Busy connections: " + busyConnections.size();
	}
}